/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "ObsPlugin.h"
#include "log/Log.h"
#include "config_reader/ConfigIniReader.h"
#include "common/EnvVarManager.h"
#include "common/CommonStruct.h"
#include "manager/CloudServiceManager.h"
#include "CommonJobFactory.h"
#include "obs_resource/AppService.h"
#include "eSDKOBS.h"
#include "alibabacloud/oss/OssClient.h"
#include "obs_service/obs_backup/ObjectStorageBackupJob.h"
#include "component/ResourceManager.h"
#include "utils/CertManager.h"
#include "PluginUtilities.h"

using namespace std;
using namespace Module;
using namespace OBSPlugin;
namespace {
static const std::string GENERALDN_LOG_NAME = "AppPlugins.log";
static const std::string GENERAL_LOG_PATH = "/DataBackup/ProtectClient/ProtectClient-E/slog/ObsPlugin";
const std::string MODULE = "ObsPlugin";
const std::string ALILOG = "ALICLOUD";
const std::string TRUE_STR = "true";
constexpr auto DEFAULT_PREFIX_SPLITTER = "/";
constexpr uint64_t BACKUP_INC_TO_FULL = 1577209901;
constexpr int RETRY_INTERVAL_TIME = 30;

void LogCallbackFunc(AlibabaCloud::OSS::LogLevel level, const std::string &stream)
{
    if (level == AlibabaCloud::OSS::LogLevel::LogOff) {
        return;
    }
    HCP_Log(INFO, ALILOG) << stream << HCPENDLOG;
}

namespace NUM {
    constexpr int I1 = 1;
    constexpr int I2 = 2;
    constexpr int I3 = 3;
    constexpr int I4 = 4;
    constexpr int I5 = 5;
    constexpr int I6 = 6;
    constexpr int I8 = 8;
    constexpr int I10 = 10;
    constexpr int I16 = 16;
    constexpr int I20 = 20;
    constexpr int I32 = 32;
    constexpr int I50 = 50;
    constexpr int I64 = 64;
    constexpr int I100 = 100;
    constexpr int I120 = 120;
    constexpr int I128 = 128;
    constexpr int I200 = 200;
    constexpr int I300 = 300;
    constexpr int I480 = 480;
    constexpr int I500 = 500;
    constexpr int I512 = 512;
    constexpr int I1000 = 1000;
    constexpr int I1024 = 1024;
    constexpr int I2000 = 2000;
    constexpr int I3000 = 3000;
    constexpr int I4000 = 4000;
    constexpr int I4800 = 4800;
    constexpr int I6000 = 6000;
    constexpr int I8000 = 8000;
    constexpr int I10000 = 10000;
    constexpr int I40000 = 40000;
    constexpr int I50000 = 50000;
    constexpr int I80000 = 80000;
    constexpr int I86400 = 86400;
    constexpr int I100000 = 100000;
    constexpr int I800000 = 800000;
    constexpr int I1000000 = 1000000;
    constexpr int I10000000 = 10000000;
    constexpr int I100000000 = 100000000;
    constexpr int MB = 1024 * 1024;
    constexpr int GB = 1024 * 1024 * 1024;

    constexpr int MP_TASK_FAILED_NO_REPORT = -15;
    constexpr int RET_BUSY = 101;
}

struct DefaultConfigTable {
    const string keyName;
    int minValue;
    int maxValue;
    int defaultValue;
};

void InitBackupConfigurations()
{
    DefaultConfigTable cfgTbl[] = {
        {"DME_OBS_SCAN_CTRL_MAX_ENTRIES_FULLBKUP", NUM::I1, NUM::I100000, NUM::I10000},
        {"DME_OBS_SCAN_CTRL_MAX_ENTRIES_INCRBKUP", NUM::I1, NUM::I100000, NUM::I1000},
        {"DME_OBS_SCAN_CTRL_MIN_ENTRIES_FULLBKUP", NUM::I1, NUM::I50000, NUM::I10000},
        {"DME_OBS_SCAN_CTRL_MIN_ENTRIES_INCRBKUP", NUM::I1, NUM::I50000, NUM::I1000},
        {"DME_OBS_SCAN_DEFAULT_META_FILE_SIZE", NUM::I1, NUM::GB, NUM::GB},
        {"DME_OBS_SCAN_LIBNFS_OPENDIR_REQ_CNT", NUM::I1, NUM::I100000, NUM::I4000},
        {"DME_OBS_SCAN_LIST_OBJECTS_NUM", NUM::I100, NUM::I1000, NUM::I1000},
        {"DME_OBS_SCAN_X6000_OPENDIR_REQ_CNT", NUM::I1, NUM::I10000, NUM::I500},
        {"DME_OBS_SCAN_X6000_MAX_WRITE_QUEUE_SIZE", NUM::I1, NUM::I10000, NUM::I1000},
        {"DME_OBS_SCAN_X6000_MAX_SCAN_QUEUE_SIZE", NUM::I1, NUM::I50000, NUM::I10000},
        {"DME_OBS_SCAN_X6000_MIN_SCAN_QUEUE_SIZE", NUM::I1, NUM::I40000, NUM::I8000},
        {"DME_OBS_SCAN_X6000_WRITE_QUEUE_SIZE", NUM::I1, NUM::I1000, NUM::I100},
        {"DME_OBS_SCAN_X6000_DIR_ENTRY_READ_COUNT", NUM::I1, NUM::I512, NUM::I512},
        {"DME_OBS_SCAN_X6000_DEFAULT_META_FILE_SIZE", NUM::I1, NUM::GB, NUM::GB},
        {"DME_OBS_SCAN_X8000_MAX_WRITE_QUEUE_SIZE", NUM::I1, NUM::I100000, NUM::I10000},
        {"DME_OBS_SCAN_X8000_MAX_SCAN_QUEUE_SIZE", NUM::I1, NUM::I1000000, NUM::I100000},
        {"DME_OBS_SCAN_X8000_MIN_SCAN_QUEUE_SIZE", NUM::I1, NUM::I800000, NUM::I80000},
        {"DME_OBS_SCAN_X8000_WRITE_QUEUE_SIZE", NUM::I1, NUM::I1000, NUM::I1000},
        {"DME_OBS_SCAN_X8000_DIR_ENTRY_READ_COUNT", NUM::I1, NUM::I1024, NUM::I1024},
        {"DME_OBS_SCAN_X8000_DEFAULT_META_FILE_SIZE", NUM::I1, NUM::GB, NUM::GB},
        {"DME_OBS_SCAN_X8000_PRODUCER_THREAD_COUNT", NUM::I1, NUM::I32, NUM::I32},
        {"DME_OBS_SCAN_X3000_WRITE_QUEUE_SIZE", NUM::I1, NUM::I1000, NUM::I100},
        {"DME_OBS_SCAN_X3000_DEFAULT_META_FILE_SIZE", NUM::I1, NUM::I1000 * NUM::MB, NUM::I100 * NUM::MB},
        {"DME_OBS_SCAN_X3000_PRODUCER_THREAD_COUNT", NUM::I1, NUM::I32, NUM::I4},
        {"DME_OBS_SCAN_CTRL_FILE_TIME_SEC", NUM::I1, NUM::I120, NUM::I5},
        {"DME_OBS_SCAN_CTRL_FILE_SIZE", NUM::I1000000, NUM::I100000000, NUM::I10000000},
        {"DME_OBS_BACKUP_NAS_SERVER_CHECK_MAX_ERR_COUNT", NUM::I10, NUM::I10000, NUM::I100},
        {"DME_OBS_BACKUP_MAX_ALI_QPS_RW", NUM::I100, NUM::I10000, NUM::I2000},
        {"DME_OBS_BACKUP_MAX_OBS_QPS_GET", NUM::I100, NUM::I10000, NUM::I10000},
        {"DME_OBS_BACKUP_MAX_OBS_QPS_PUT", NUM::I100, NUM::I10000, NUM::I6000},
        {"DME_OBS_BACKUP_MAX_TOTAL_BLOCK_BUFFER_CNT", NUM::I100, NUM::I10000, NUM::I500},
        {"DME_OBS_BACKUP_MAX_TOTAL_BLOCK_BUFFER_SIZE", NUM::I10 * NUM::MB, NUM::I1024 * NUM::MB, NUM::I50 * NUM::MB},
        {"DME_OBS_BACKUP_BLOCK_SIZE", NUM::I1 * NUM::MB, NUM::I8 * NUM::MB, NUM::I5 * NUM::MB},
        {"DME_OBS_BACKUP_SERVER_MAX_MEMORY_SIZE", NUM::I10 * NUM::MB, NUM::I1024 * NUM::MB, NUM::I50 * NUM::MB},
        {"DME_OBS_BACKUP_SERVER_THREAD_NUM", NUM::I2, NUM::I64, NUM::I32},
        {"DME_OBS_BACKUP_META", 0, NUM::I1, NUM::I1},
        {"BACKUP_STUCK_TIME", NUM::I120, NUM::I86400, NUM::I3000},
        {"DME_OBS_AGGR_THREAD_POOL_CNT", NUM::I1, NUM::I128, NUM::I8},
        {"DME_OBS_PREFIX_SPLIT_DEPTH", NUM::I1, NUM::I16, NUM::I3},
        {"DME_OBS_BACKUP_BLOCK_NUM", NUM::I1, NUM::I10000, NUM::I10000},
    };

    for (size_t i = 0; i < (sizeof(cfgTbl) / sizeof(cfgTbl[0])); i++) {
        SetPluginConfigInt(cfgTbl[i].keyName, cfgTbl[i].minValue, cfgTbl[i].maxValue, cfgTbl[i].defaultValue);
    }
}

void InitBackupConfigurations2()
{
    DefaultConfigTable cfgTbl[] = {
        {"DME_OBS_COMPRESS_META_DATA", 0, 1, 0},
        {"DME_OBS_ENCODING_FLAG", 0, 1, 0},
        {"DME_OBS_SCAN_PARALLEL_SWITCH", 0, 1, 1},
    };

    for (size_t i = 0; i < (sizeof(cfgTbl) / sizeof(cfgTbl[0])); i++) {
        SetPluginConfigInt(cfgTbl[i].keyName, cfgTbl[i].minValue, cfgTbl[i].maxValue, cfgTbl[i].defaultValue);
    }
}

void InitBackupConfigurationsParameter()
{
    SetPluginConfigString("DME_OBS_SCAN_CTRL_MAX_DATASIZE", "10737418240");
    SetPluginConfigString("DME_OBS_SCAN_CTRL_MIN_DATASIZE", "5368709120");
    SetPluginConfigString("DME_OBS_PREFIX_SPLITTER", DEFAULT_PREFIX_SPLITTER);
    SetPluginConfigString("OBS_SQLITE_DIR", "/tmp");
    SetPluginConfigString("DME_OBS_ENCODING_CFG", "");
    SetPluginConfigString("DME_OBS_BACKUP_EXCLUSE_META", "Content-Length,Content-Range,ETag,crc64ecma");
}
}  // namespace

// Agent安装部署时若勾选了此插件会先调用这个进行初始化
OBSPLUGIN_API int AppInit(std::string &logPath)
{
    string logFilePath;
    if (logPath.empty()) {
        logFilePath = Module::EnvVarManager::GetInstance()->GetAgentHomePath() + GENERAL_LOG_PATH;
    } else {
        logFilePath = logPath;
    }
    int iLogLevel = ConfigReader::getInt(string(MS_CFG_GENERAL_SECTION), string(MS_CFG_LOG_LEVEL));
    int iLogCount = ConfigReader::getInt(string(MS_CFG_GENERAL_SECTION), string(MS_CFG_LOG_COUNT));
    int iLogMaxSize = ConfigReader::getInt(string(MS_CFG_GENERAL_SECTION), string(MS_CFG_LOG_MAX_SIZE));
    // hcpconf.ini中读取配置并初始化
    CLogger::GetInstance().Init(GENERALDN_LOG_NAME.c_str(), logFilePath);
    CLogger::GetInstance().SetLogConf(iLogLevel, iLogCount, iLogMaxSize);
    obs_initialize(OBS_INIT_ALL);
    // alicloud init
    AlibabaCloud::OSS::InitializeSdk();
    AlibabaCloud::OSS::SetLogLevel(AlibabaCloud::OSS::LogLevel::LogError);
    AlibabaCloud::OSS::SetLogCallback(LogCallbackFunc);

    InitBackupConfigurations();
    InitBackupConfigurations2();
    InitBackupConfigurationsParameter();
    Module::ConfigReader::refresh(Module::ConfigReader::getConfigFiles());
    return 0;
}

OBSPLUGIN_API void CheckApplication(
    ActionResult &returnValue, const ApplicationEnvironment &appEnv, const Application &application)
{
    AppServiceExport::CheckApplication(returnValue, appEnv, application);
    return;
}

// 列举资源不实现该接口
OBSPLUGIN_API void ListApplicationResource(vector<ApplicationResource> &returnValue,
    const ApplicationEnvironment &appEnv, const Application &application, const ApplicationResource &parentResource)
{
    (void)returnValue;
    (void)appEnv;
    (void)application;
    (void)parentResource;
    return;
}

// /** list resource result with page */
// struct ResourceResultByPage {
//     /** resource elements list */
//     1:required list<ApplicationResource> items,
//     /** current page no */
//     2:required i32 pageNo,
//     /** maximum number of elements in one page */
//     3:required i32 pageSize,
//     /** total page number */
//     4:required i32 pages,
//     /** total elements number */
//     5:required i32 total
// }
OBSPLUGIN_API void ListApplicationResourceV2(ResourceResultByPage &returnValue, const ListResourceRequest &request)
{
    AppServiceExport::ListApplicationResourceV2(returnValue, request);
}

OBSPLUGIN_API void QueryJobPermission(AppProtect::JobPermission& returnJobPermission, const Application& application)
{
    (void)returnJobPermission;
    (void)application;
}

OBSPLUGIN_API void AllowBackupInLocalNode(ActionResult& returnValue, const AppProtect::BackupJob& job,
    const AppProtect::BackupLimit::type limit)
{
    HCP_Log(INFO, MODULE) << "Enter object storage plugin AllowBackupInLocalNode." << HCPENDLOG;

    returnValue.__set_code(0);
}

OBSPLUGIN_API void AllowBackupSubJobInLocalNode(
    ActionResult& returnValue, const AppProtect::BackupJob& job, const AppProtect::SubJob& subJob)
{
    HCP_Log(INFO, MODULE) << "Enter object storage plugin AllowBackupSubJobInLocalNode,subJobId:"
        << subJob.subJobId << HCPENDLOG;
    ObjectStorageBackupSubJob subJobInfo {};
    if (!subJob.jobInfo.empty() && !Module::JsonHelper::JsonStringToStruct(subJob.jobInfo, subJobInfo)) {
        HCP_Log(ERR, MODULE) << "Get backup subjob info failed" << HCPENDLOG;
        returnValue.__set_code(Module::OBS_INTERNAL_ERROR_CODE);
        return;
    }
    HCP_Log(INFO, MODULE) << "AllowBackupSubJobInLocalNode,subJobType:"<< subJobInfo.m_SubTaskType << HCPENDLOG;
    if (subJobInfo.m_SubTaskType == SUBJOB_TYPE_PREFIX_SCAN_PHASE) {
        int scanTaskCount = ResourceManager::GetInstance().GetKeyCount();
        HCP_Log(DEBUG, MODULE) << "AllowBackupSubJobInLocalNode,scanTaskCount:"<< scanTaskCount << HCPENDLOG;
        if (scanTaskCount > 0) {
            returnValue.__set_bodyErr(NUM::MP_TASK_FAILED_NO_REPORT);
            returnValue.__set_code(NUM::RET_BUSY);
            HCP_Log(WARN, MODULE) << "Scanner is in progress, subJob can't run, current subJobId:"
                << subJob.subJobId << HCPENDLOG;
            return;
        }
    }
    CheckObsConnect(returnValue, job);
}

void CheckObsConnect(ActionResult& returnValue, const AppProtect::BackupJob& job)
{
    OBSPlugin::ObjectStorageAuthExtendInfo authInfo;
    if (!Module::JsonHelper::JsonStringToStruct(job.protectEnv.auth.extendInfo, authInfo)) {
        HCP_Log(ERR, MODULE) << "parse GetAuthExtendInfo failed. authExtendInfo is: "
            << WIPE_SENSITIVE(job.protectEnv.auth.extendInfo) << "jobId is: " << job.jobId << HCPENDLOG;
        returnValue.__set_code(Module::OBS_INTERNAL_ERROR_CODE);
        return;
    }
    CheckObsConnectInner(returnValue, authInfo.getStorageType(), authInfo);
}

void CheckObsConnectInner(ActionResult& returnValue, const Module::StorageType& storageType,
    const OBSPlugin::ObjectStorageAuthExtendInfo& authInfo)
{
    std::string certContent = OBSPlugin::Base64Decode(authInfo.certification);
    CertManager certMgr(authInfo.ak);
    (void)certMgr.SaveFile(certContent);
    Module::StorageConfig storageConfig;
    storageConfig.storageType = storageType;
    storageConfig.verifyInfo.accessKey = authInfo.ak;
    storageConfig.verifyInfo.secretKey = authInfo.sk;
    storageConfig.verifyInfo.endPoint = authInfo.endPoint;
    storageConfig.verifyInfo.useHttps = authInfo.openHttps();
    storageConfig.verifyInfo.certHttps = certContent;
    storageConfig.verifyInfo.caPath = certMgr.GetCAPath();
    storageConfig.verifyInfo.caFile = certMgr.GetCAFile();
    storageConfig.verifyInfo.useProxy = authInfo.openProxy();
    storageConfig.verifyInfo.proxyHostName = authInfo.proxyHostName;
    storageConfig.verifyInfo.proxyUserName = authInfo.proxyUserName;
    storageConfig.verifyInfo.proxyUserPwd = authInfo.proxyUserPwd;
    auto cloudService = CloudServiceManager::CreateInst(storageConfig);
    if (cloudService == nullptr) {
        HCP_Log(ERR, MODULE) << "CreateInst Failed" << HCPENDLOG;
        returnValue.__set_code(Module::OBS_INTERNAL_ERROR_CODE);
        certMgr.RemoveCertFile();
        return;
    }
    auto checkConnectRequest = std::make_unique<CheckConnectRequest>();
    // 可能存在并发访问服务端太快，服务端返回连接失败的情况，增加重试间隔时间
    checkConnectRequest->retryConfig.retryInterval = RETRY_INTERVAL_TIME;
    OBSResult result = cloudService->CheckConnect(checkConnectRequest);
    if (result.IsSucc()) {
        returnValue.__set_code(0);
    } else {
        int64_t errorCode = result.GetCommonErrorCode();
        if (errorCode == Module::OBS_INTERNAL_ERROR_CODE) {
            HCP_Log(ERR, MODULE) << "UNKNOW ERROR" << HCPENDLOG;
            errorCode = Module::OBS_CONNECTIVITY_ERROR_CODE;
        }
        returnValue.__set_code(errorCode);
        returnValue.__set_bodyErr(errorCode);
        returnValue.__set_message(result.errorDesc);
        HCP_Log(ERR, MODULE) << "CheckApplication Failed" << HCPENDLOG;
    }
    certMgr.RemoveCertFile();
}

OBSPLUGIN_API void AllowRestoreInLocalNode(ActionResult& returnValue, const AppProtect::RestoreJob& job)
{
    HCP_Log(INFO, MODULE) << "Enter object storage plugin AllowRestoreInLocalNode." << HCPENDLOG;
    (void)job;
    returnValue.__set_code(0);
    return ;
}

OBSPLUGIN_API void AllowRestoreSubJobInLocalNode(
    ActionResult& returnValue, const AppProtect::RestoreJob& job, const AppProtect::SubJob& subJob)
{
    HCP_Log(INFO, MODULE) << "Enter object storage plugin AllowRestoreSubJobInLocalNode." << HCPENDLOG;

    OBSPlugin::ObjectStorageAuthExtendInfo authInfo;
    if (!Module::JsonHelper::JsonStringToStruct(job.targetEnv.auth.extendInfo, authInfo)) {
        HCP_Log(ERR, MODULE) << "parse GetAuthExtendInfo failed. authExtendInfo is: "
            << WIPE_SENSITIVE(job.targetObject.auth.extendInfo) << "jobId is: " << job.jobId << HCPENDLOG;
        returnValue.__set_code(Module::OBS_INTERNAL_ERROR_CODE);
        return;
    }
    OBSPlugin::ObjectStorageTargetEnvExtend targetEnvExtend;
    if (!Module::JsonHelper::JsonStringToStruct(job.targetEnv.extendInfo, targetEnvExtend)) {
        HCP_Log(ERR, MODULE) << "extendInfo JsonStringToStruct failed" << HCPENDLOG;
        returnValue.__set_code(Module::OBS_INTERNAL_ERROR_CODE);
        return;
    }
    Module::StorageType storageType = static_cast<Module::StorageType>(stoi(targetEnvExtend.storageType));
    CheckObsConnectInner(returnValue, storageType, authInfo);
}

OBSPLUGIN_API void CheckBackupJobType(ActionResult& returnValue, const AppProtect::BackupJob& job)
{
    HCP_Log(INFO, MODULE) << "Enter object storage plugin CheckBackupJobType." << HCPENDLOG;
    int ret = Module::SUCCESS;
    auto jobCommonInfoPtr = make_shared<JobCommonInfo>(make_shared<BackupJob>(job));
    auto jobptr = std::make_shared<ObjectStorageBackupJob>();
    jobptr->SetJobInfo(jobCommonInfoPtr);
    ret = jobptr->CheckBackupJobType();
    if (ret != Module::SUCCESS) {
        HCP_Log(INFO, MODULE) << "Exit OBS plugin CheckBackupJobType, report BACKUP_INC_TO_FULL." << HCPENDLOG;
        returnValue.__set_bodyErr(BACKUP_INC_TO_FULL); // Specified error code, ubc will do INC to FULL
        returnValue.__set_code(Module::OBS_INTERNAL_ERROR_CODE);
        return;
    }
    returnValue.__set_code(0);
    HCP_Log(INFO, MODULE) << "Exit object storage plugin CheckBackupJobType." << HCPENDLOG;
    return;
}

OBSPLUGIN_API JobFactoryBase* CreateFactory()
{
    HCP_Log(INFO, MODULE) << "Enter obs plugin create factory." << HCPENDLOG;
    return OBSPlugin::CommonJobFactory::GetInstance();
}
